/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "FusionStorageBitmapHandle.h"
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/scope_exit.hpp>
#include "FusionStorageApi.h"
#include "DiskDataPersistence.h"
#include "log/Log.h"
#include "system/System.hpp"
#include "common/utils/Utils.h"

namespace {
const std::string MODULE_NAME = "FusionStorageBitmap";
const std::string DSWARE_HANDLE_DETACH_DELETE_BITMAP_FAILED = "DETACH_DELETE_BITMAP_FAILED";
const std::string DSWARE_HANDLE_DETACH_BITMAP_FAILED = "DETACH_BITMAP_FAILED";
const std::string DSWARE_HANDLE_DELETE_BITMAP_FAILED = "DELETE_BITMAP_FAILED";
const int RETRY_INTERVAL = 30;
const int RETRY_TIMES = 3;

const int CONSTANT_NUM_4 = 4;
const int CONSTANT_NUM_6 = 6;
const int CONSTANT_NUM_8 = 8;
const int KB_UNIT = 1024;
const size_t PAGE_SIZE = CONSTANT_NUM_4 * KB_UNIT;  // 4KB
const int STANDARD_START_TIME_1970 = 1970;
}  // namespace

VIRT_PLUGIN_NAMESPACE_BEGIN

FusionStorageBitmapHandle::FusionStorageBitmapHandle(const std::string &fusionStorMgrIp, const std::string &poolID,
    const bool isBackup, const std::string &volumeID, std::shared_ptr<DiskDataPersistence> diskDataPersistence,
    std::shared_ptr<RepositoryHandler> cacheRepoHandler, const std::string cacheRepoPath)
    : m_fusionStorMgrIp(fusionStorMgrIp), m_trunkSize(0), m_diskSize(0), m_bitmapVolumeSizeInBytes(0), m_diskNum(0),
      m_isBackup(isBackup), m_strVolumeID(volumeID), m_diskDataPersistence(diskDataPersistence),
      m_cacheRepoHandler(cacheRepoHandler), m_cacheRepoPath(cacheRepoPath)
{
    m_spDeviceFile = std::make_shared<DiskDeviceFile>();
}

FusionStorageBitmapHandle::~FusionStorageBitmapHandle()
{}

void FusionStorageBitmapHandle::InitializeFusionStorageApi(std::string &apiMode,
    std::shared_ptr<FusionStorageRestApiOperator> fusionStorageRestApiOperator,
    std::shared_ptr<FusionStorageApi> fusionStorageApi)
{
    m_apiMode = apiMode;
    if (m_apiMode == "VBS" && fusionStorageApi != nullptr) {
        m_spDSWareApi = fusionStorageApi;
    }
    if (m_apiMode == "ISCSI" && fusionStorageRestApiOperator != nullptr) {
        m_spDSWareRestApi = fusionStorageRestApiOperator;
    }
}

int32_t FusionStorageBitmapHandle::CreateAndAttachBitmapVolume(BitmapVolumeInfo &bitmapVolumeInfo, std::string &errMsg)
{
    std::string bitmapFile = m_cacheRepoPath + VIRT_PLUGIN_CACHE_ROOT + bitmapVolumeInfo.snapNameTo +
        "_bitmap_volume.info";
    if (m_cacheRepoHandler == nullptr) {
        ERRLOG("Cache repohandle is null.");
        return FAILED;
    }
    int ret;
    if (!m_cacheRepoHandler->Exists(bitmapFile)) {
        if (CreateBitmapVolume(bitmapVolumeInfo, errMsg) != SUCCESS) {
            ERRLOG("Create bitmap vloume failed.");
            return FAILED;
        }
    } else  {
        INFOLOG("File %s is exist, dont need create new bitmap.", bitmapFile.c_str());
        bool isLastBitmap = bitmapVolumeInfo.isLastBitmap;
        std::string infoStr;
        Utils::ReadFile(m_cacheRepoHandler, bitmapFile, infoStr);
        Module::JsonHelper::JsonStringToStruct(infoStr, bitmapVolumeInfo);
        // 第一次获取bitmap但缓存有bitmap文件，可能是因为下发了创建命令，但在查询过程中插件重启。此时需要重新等待bitmap卷创建完成
        if (bitmapVolumeInfo.isFirstBitmap && RetryToQueryBitmap(bitmapVolumeInfo) != SUCCESS) {
            ERRLOG("Query bitmap volume %s failed.", bitmapVolumeInfo.volName.c_str());
            return FAILED;
        }
        bitmapVolumeInfo.isLastBitmap = isLastBitmap;
    }
    m_trunkSize = bitmapVolumeInfo.blockSize;
    m_bitmapVolumeSizeInBytes = static_cast<uint64_t>(bitmapVolumeInfo.snapSize) * KB_UNIT * KB_UNIT;
    if (m_trunkSize == 0) {
        errMsg = "Unknown error.";
        ERRLOG("Data trunk size is zero.");
        return FAILED;
    }
    if (!m_bitmapVolumeDiskPath.empty()) {
        INFOLOG("Bitmap volume %s has attached.", bitmapVolumeInfo.volName.c_str());
        return SUCCESS;
    }
    if (m_apiMode == "VBS") {
        ret = RetryOp(boost::bind(&FusionStorageApi::AttachVolume, m_spDSWareApi, bitmapVolumeInfo.volName,
            boost::ref(m_bitmapVolumeDiskPath), boost::ref(errMsg), m_cacheRepoHandler,
            m_cacheRepoPath + VIRT_PLUGIN_CACHE_ROOT), "AttachVolume");
    } else {
        ret = RetryOp(boost::bind(&FusionStorageRestApiOperator::AttachBitmapVolume, m_spDSWareRestApi,
        bitmapVolumeInfo.volName, boost::ref(m_bitmapVolumeDiskPath), boost::ref(errMsg)), "AttachVolume");
    }
    if (ret != DSWARE_API_OK) {
        ERRLOG("Create diffrent bitmap failed.");
        return ret;
    }

    return SUCCESS;
}

int32_t FusionStorageBitmapHandle::RetryToQueryBitmap(BitmapVolumeInfo &info)
{
    int32_t ret;
    if (m_apiMode == "VBS") {
        ret = m_spDSWareApi->RetryToQueryBitmap(info);
    } else {
        ret = m_spDSWareRestApi->RetryToQueryBitmap(info);
    }
    if (ret != SUCCESS) {
        ERRLOG("Query diffrent bitmap failed.");
        return ret;
    }
    INFOLOG("Query bitmap volume %s success.", info.volName.c_str());
    return SUCCESS;
}

int32_t FusionStorageBitmapHandle::CreateBitmapVolume(BitmapVolumeInfo &bitmapVolumeInfo, std::string &errMsg)
{
    std::string bitmapFile = m_cacheRepoPath + VIRT_PLUGIN_CACHE_ROOT + bitmapVolumeInfo.snapNameTo +
        "_bitmap_volume.info";
    bitmapVolumeInfo.volName = GetBitmapVolName();
    INFOLOG("Start to create volume bitmap, volume name: %s, dswareMgr IP: %s, snapNameFrom: %s, snapNameTo: %s",
        bitmapVolumeInfo.volName.c_str(), m_fusionStorMgrIp.c_str(),
        bitmapVolumeInfo.snapNameFrom.c_str(), bitmapVolumeInfo.snapNameTo.c_str());
    m_diskDataPersistence->AddObject(FusionStorageDef::DSWARE_MGR_IP, m_fusionStorMgrIp);
    m_diskDataPersistence->AppendArrayElement(FusionStorageDef::BITMAP_VOLUMES_CREATED, bitmapVolumeInfo.volName);
    m_diskDataPersistence->AppendArrayElement(FusionStorageDef::BITMAP_VOLUMES_ATTACHED, bitmapVolumeInfo.volName);
    int ret;
    if (m_apiMode == "VBS") {
        m_diskDataPersistence->AddObject(FusionStorageDef::DSWARE_POOL_ID, m_spDSWareApi->GetPoolID());
        ret = RetryOp(boost::bind(&FusionStorageApi::CreateBitmapVolume, m_spDSWareApi, m_cacheRepoHandler,
            boost::ref(bitmapFile), boost::ref(bitmapVolumeInfo), boost::ref(errMsg)), "CreateBitmapVol");
    } else {
        m_diskDataPersistence->AddObject(FusionStorageDef::DSWARE_POOL_ID, m_spDSWareRestApi->GetPoolID());
        ret = RetryOp(boost::bind(&FusionStorageRestApiOperator::CreateBitmapVolume, m_spDSWareRestApi,
            m_cacheRepoHandler, boost::ref(bitmapFile), boost::ref(bitmapVolumeInfo), boost::ref(errMsg)),
            "CreateBitmapVol");
    }
    if (ret == DSWARE_API_ERR_SNAP_IS_NOT_EXIST && !bitmapVolumeInfo.usedInRestore) {
        bitmapVolumeInfo.snapNameFrom.clear();
        ret = RetryOp(boost::bind(&FusionStorageApi::CreateBitmapVolume, m_spDSWareApi, m_cacheRepoHandler,
            boost::ref(bitmapFile), boost::ref(bitmapVolumeInfo), boost::ref(errMsg)), "CreateBitmapVol");
    }

    if (ret != DSWARE_API_OK) {
        ERRLOG("Create diffrent bitmap failed.");
        return ret;
    }
    INFOLOG("Create bitmap volume %s success.", bitmapVolumeInfo.volName.c_str());
    return SUCCESS;
}

int32_t FusionStorageBitmapHandle::DeleteAndDetachapVolume(const std::string &volumeName, std::string &errMsg)
{
    DBGLOG("Delete and Detach bitmap volume %s.", volumeName.c_str());
    int ret = SUCCESS;
    int volumeRet;

    if (m_apiMode == "VBS") {
        volumeRet = RetryOp(boost::bind(&FusionStorageApi::DetachVolume, m_spDSWareApi, volumeName, boost::ref(errMsg),
            m_cacheRepoHandler, m_cacheRepoPath + VIRT_PLUGIN_CACHE_ROOT), "DetachVolume");
    } else {
        volumeRet = RetryOp(
            boost::bind(&FusionStorageRestApiOperator::DetachVolume, m_spDSWareRestApi, volumeName, boost::ref(errMsg)),
            "DetachVolume");
    }

    if (volumeRet != DSWARE_API_OK) {
        ret = FAILED;
        ERRLOG("Detach volume [%s] failed.", volumeName.c_str());
        errMsg = DSWARE_HANDLE_DETACH_BITMAP_FAILED;
    } else {
        m_diskDataPersistence->RemoveArrayElement(FusionStorageDef::BITMAP_VOLUMES_ATTACHED, volumeName);
    }

    if (m_apiMode == "VBS") {
        volumeRet =
            RetryOp(boost::bind(&FusionStorageApi::DeleteBitmapVolume, m_spDSWareApi, volumeName, boost::ref(errMsg)),
                "DeleteBitmapVolume");
    } else {
        volumeRet = RetryOp(
            boost::bind(
                &FusionStorageRestApiOperator::DeleteBitmapVolume, m_spDSWareRestApi, volumeName, boost::ref(errMsg)),
            "DeleteBitmapVolume");
    }
    if (volumeRet != DSWARE_API_OK) {
        ret = FAILED;
        ERRLOG("Delete volume[%s] failed.", volumeName.c_str());
        errMsg = DSWARE_HANDLE_DELETE_BITMAP_FAILED;
    } else {
        m_diskDataPersistence->RemoveArrayElement(FusionStorageDef::BITMAP_VOLUMES_CREATED, volumeName);
    }

    return ret;
}

int32_t FusionStorageBitmapHandle::CalculateDirtyRange(uint64_t startOffset, DirtyRanges &dirtyRanges)
{
    uint64_t startAddr = startOffset / (CONSTANT_NUM_8 * m_trunkSize);
    DBGLOG("Begin calculate dirtyrange. startOffset=%lld, startAddr=%lld, chunkSize=%lld, diskSize=%lld,\
        bitmapSizeInBytes=%lld", startOffset, startAddr, m_trunkSize, m_diskSize, m_bitmapVolumeSizeInBytes);

    if (startOffset % (CONSTANT_NUM_8 * m_trunkSize) > 0) {
        WARNLOG("Can not be divided. startOffset: %lld, m_trunkSize: %lld.", startOffset, m_trunkSize);
    }

    uint64_t endAddr = (m_diskSize + startOffset + (CONSTANT_NUM_8 * m_trunkSize - 1)) / (CONSTANT_NUM_8 * m_trunkSize);
    DiskScannerHandler::GetInstance()->ChangeFilePriviledge(m_bitmapVolumeDiskPath, VolOpenMode::READ_ONLY);
    DISK_DEVICE_RETURN_CODE devRetCode = m_spDeviceFile->Open(m_bitmapVolumeDiskPath, O_RDONLY, endAddr);
    if (devRetCode != DISK_DEVICE_OK) {
        ERRLOG("Open disk device[%s] file failed.", m_bitmapVolumeDiskPath.c_str());
        return FAILED;
    }

    std::shared_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(PAGE_SIZE);
    if (buf == nullptr) {
        ERRLOG("Alloc memory failed.");
        return FAILED;
    }

    int32_t iRet = SUCCESS;
    while (startAddr < endAddr) {
        memset_s(buf.get(), PAGE_SIZE, 0, PAGE_SIZE);
        uint64_t bufferSize = PAGE_SIZE;
        devRetCode = m_spDeviceFile->Read(startAddr, bufferSize, buf);
        if (devRetCode != DISK_DEVICE_OK) {
            iRet = FAILED;
            ERRLOG("Read disk device file failed.");
            break;
        }

        for (uint64_t i = 0; i < bufferSize; ++i) {
            if (startAddr == endAddr) {
                DBGLOG("startAddr: %lld, endAddr: %lld.", startAddr, endAddr);
                break;
            }
            if (!AddDitryRange(endAddr, startAddr, buf[i], dirtyRanges)) {
                iRet = FAILED;
                ERRLOG("AddDitryRange failed.");
                break;
            }
            startAddr++;
        }
    }

    devRetCode = m_spDeviceFile->Close();
    if (devRetCode != DISK_DEVICE_OK) {
        ERRLOG("Close disk device file failed. Err code: %d.", devRetCode);
    }
    return iRet;
}

int32_t FusionStorageBitmapHandle::GetDirtyRanges(
    const DirtyRangesParams &params, DirtyRanges &dirtyRanges, std::string &errMsg, BitmapVolumeInfo &bitmapVolumeInfo)
{
    DBGLOG("StartOffset: %lld, volumeSize: %lld.", params.offset, params.diskSize);
    bitmapVolumeInfo.snapNameFrom = params.parentVolumeID;
    bitmapVolumeInfo.snapNameTo = params.volumeID;
    m_snapshotUUID = params.volumeID;
    bitmapVolumeInfo.usedInRestore = dirtyRanges.IsRestore();
    m_diskSize = params.diskSize;
    int32_t iRet = CreateAndAttachBitmapVolume(bitmapVolumeInfo, errMsg);
    std::string err;
    if (iRet != DSWARE_API_OK) {
        ERRLOG("Create or open bitmap failed.");
        (void)DeleteAndDetachapVolume(bitmapVolumeInfo.volName, err);
        return iRet;
    }

    if (bitmapVolumeInfo.snapNameFrom.empty()) {
        INFOLOG("Parent snapshot not exist,will do Full Backup.");
        dirtyRanges.UseFull();
    }

    iRet = CalculateDirtyRange(params.offset, dirtyRanges);
    if (iRet != SUCCESS) {
        ERRLOG("Caculate dirty range  failed.");
        (void)DeleteAndDetachapVolume(bitmapVolumeInfo.volName, err);
        return iRet;
    }
    return SUCCESS;
}

int32_t FusionStorageBitmapHandle::RetryOp(boost::function<int()> fun, const std::string &opName)
{
    if (fun == nullptr) {
        ERRLOG("Operation[%s] failed.", opName.c_str());
        return FAILED;
    }

    int32_t iRet = FAILED;
    int32_t tryTimes = 0;
    while (tryTimes < RETRY_TIMES + 1) {
        iRet = fun();
        if (iRet == DSWARE_API_OK) {
            return SUCCESS;
        }
        if (iRet == DSWARE_API_ERR_SNAP_IS_NOT_EXIST) {
            return DSWARE_API_ERR_SNAP_IS_NOT_EXIST;
        }
        tryTimes++;
        if (tryTimes == RETRY_TIMES) {
            break;
        }
        std::this_thread::sleep_for(std::chrono::seconds(RETRY_INTERVAL));
        WARNLOG("Operation[%s] failed. Retry %d time.", opName.c_str(), tryTimes);
    }

    ERRLOG("Operation[%s] failed.", opName.c_str());
    return iRet;
}

std::string FusionStorageBitmapHandle::GetBitmapVolName()
{
    return m_bitmapName;
}

void FusionStorageBitmapHandle::SetBitmapName(const std::string &snapshotId, const VolSnapInfo &snapshotInfo)
{
    std::string extenInfo;
    if (snapshotInfo.m_snapshotDescription.find("_Time:") == std::string::npos) {
        boost::posix_time::ptime nycTime = boost::posix_time::microsec_clock::universal_time();
        boost::posix_time::ptime timeEpoch(boost::gregorian::date(STANDARD_START_TIME_1970, 1, 1));
        boost::posix_time::time_duration diff = nycTime - timeEpoch;
        extenInfo = "_" + boost::lexical_cast<std::string>(diff.total_microseconds()) + "_BITMAP";
    } else {
        int32_t pos = snapshotInfo.m_snapshotDescription.find("_Time:");
        extenInfo = "_" + snapshotInfo.m_snapshotDescription.substr(pos + CONSTANT_NUM_6) + "_BITMAP";
    }
    m_bitmapName = snapshotId + extenInfo;
}

bool FusionStorageBitmapHandle::AddDitryRange(
    uint64_t endAddr, uint64_t curAddr, unsigned int c, DirtyRanges &dirtyRanges)
{
    if (curAddr == endAddr - 1) {
        if (c != 0) {
            uint64_t offset = curAddr * CONSTANT_NUM_8 * m_trunkSize;
            if (!dirtyRanges.AddRange(DirtyRange(offset, m_diskSize - offset))) {
                ERRLOG("AddRange failed.");
                return false;
            }
        }
        return true;
    }

    if (c == 0) {
        return true;
    } else {
        uint64_t offset = curAddr * CONSTANT_NUM_8 * m_trunkSize;
        if (!dirtyRanges.AddRange(DirtyRange(offset, m_trunkSize * CONSTANT_NUM_8))) {
            ERRLOG("AddRange failed.");
            return false;
        }
    }
    return true;
}

VIRT_PLUGIN_NAMESPACE_END
